/**
 * A class to provide access to the content
 * of a display file, in a DOM independent way
 * 
 * Currently implemented using DOM
 * @author JJG
 */

package fr.emse.tatiana.display;

import javax.xml.parsers.*;

import org.w3c.dom.*;
import org.xml.sax.*;

import fr.emse.tatiana.replayable.Anchor;

import java.io.*;

public class DisplayFileReader {
	// Some constants, should we change elements names...
	static final String DISP = "display";
	static final String ITEM = "item";
	static final String INFO = "info";
	static final String NAME = "name";
	static final String TYPE = "type";
	
	String filename; // The file name of the display file
	Element disp;  // The "display" element, which is the root
	Element citem; // The current "item" element
	Element cinfo; // The current "info" element
	String infoname; // The "name" associated to the "info" element
	Node infonode; // The node contained inside the info element
	String infotype; // The estimated "type" of the node
	Object infovalue; // The estimated "value" of this node
	Node cnode;  // A working node
	int itemcnt;  // The number of items
	// int infocnt;  // The number of info in the current item
	boolean updated; // Is current info computed ?
	
	/**
	 * Create a new DisplayFileReader
	 * The object is created as "empty"
	 */
	public DisplayFileReader()
	{
		filename = "";
		disp = null;
		citem = null;
		cinfo = null;
		infonode = null;
		cnode = null;
		itemcnt = 0;
		// infocnt = 0;
		infoname = "";
		infotype = "";
		infovalue = null;
		updated = false;
	}
	
	/**
	 * Create a new DisplayFileReader
	 * Parameter is a file name to open 
	 * @param fn A string representing the file name to open
	 */
	public DisplayFileReader(String fn)
	{
		Document docmodel;
		Element racmodel;
		Node fitem;
		Node finfo;
		int itc;
		// int ifc;

		filename = "";
		disp = null;
		citem = null;
		cinfo = null;
		infonode = null;
		cnode = null;
		itemcnt = 0;
		// infocnt = 0;
		infoname = "";
		infotype = "";
		infovalue = null;
		updated = false;
		
	    try{
	        // Construct a document factory
	        DocumentBuilderFactory fabrique = 
	        	DocumentBuilderFactory.newInstance();
	  
	        // Construct a document builder
	        DocumentBuilder constructeur = 
	        	fabrique.newDocumentBuilder();
	  
	        // Read the document model with DOM
	        File xmlmodel = new File(fn);
	        docmodel = constructeur.parse(xmlmodel);
	  
	        // Check that the document is a "display"
	        if (docmodel == null
            || (docmodel.getNodeType() != org.w3c.dom.Node.DOCUMENT_NODE))
	        {
	            // There is a problem
	            System.out.println("*** Unable to open document "+fn);
	        }
	        racmodel = docmodel.getDocumentElement();
	        if (! racmodel.getNodeName().equals(DISP))
	        {
	             System.out.println("*** " + fn + " Not a display file   ("+
	            		 racmodel.getNodeName()+")");
	             
	        }
			disp = racmodel;
	        fitem = racmodel.getFirstChild();
	        while (fitem != null
	        && fitem.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE
	        && ! fitem.getNodeName().equals(ITEM))
	        {
	      	   fitem = fitem.getNextSibling();
	        }
	        if (fitem == null)
	        {
	           System.out.println("*** No \""+ITEM+"\" in document "+fn);
		       //  return; just creates an empty replayable
	           finfo = null;
	        }
	        else
	        {
	        	finfo = fitem.getFirstChild();
	        }
            while (finfo != null
            && finfo.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE
	        && ! finfo.getNodeName().equals(INFO))
	        {
      	       finfo = finfo.getNextSibling();
            }
            if (finfo == null)
	        {
	           System.out.println("*** No \""+INFO+"\" in first \""+
	        		   ITEM+"\" of document "+fn);
	           
	        }
            cnode = fitem;
            itc = 0;
            while (cnode != null)
            {
            	if (cnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE
                && cnode.getNodeName().equals(ITEM))
            		itc++;
            	cnode = cnode.getNextSibling();
            }
            cnode = finfo;
            /*
            ifc = 0;
            while (cnode != null)
            {
            	if (cnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE
            	&& cnode.getNodeName().equals(INFO))
            		ifc++;
            	cnode = cnode.getNextSibling();
            }
            */
	  		filename = fn;
			disp = racmodel;
			citem = (Element)fitem;
			cinfo = (Element)finfo;
			itemcnt = itc;
			// infocnt = ifc;
	     
	    }catch(ParserConfigurationException pce){
	            System.out.println("DOM Parser configuration error");
	    }catch(SAXException se){
	            System.out.println("Parse document error");
	    }catch(IOException ioe){
	            System.out.println("XML I/O Error");
	            ioe.printStackTrace();
	    }

	}
	
	/**
	 * This makes its best to decode the value inside the current info element ;
	 * it position the name, type, node and value.
	 * @param pty : the "preferred type" for this object
	 * @return the type (a string) of the value
	 */
	public String updateInfoValue(String pty)
	{
		String s ;
		Node in,src;
		infoname = "";
		infotype = "";
		infovalue = null;
		infonode = null;
		pty = pty.trim().toLowerCase();
		if (cinfo != null
		&& cinfo.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE
        && cinfo.getNodeName().equals(INFO))
        {
			infoname = cinfo.getAttribute(NAME).trim().toLowerCase();
            // Find first interesting child  
            src = in = cinfo.getFirstChild();
            // Try to find in "in" an element
            while (in != null && in.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE)
            {
                in = in.getNextSibling();
            }
            // No child found - the object is empty
            if (src == null)
            {
            	infotype ="null";
            }
            else
            if (in == null)
            {
            	// A content, but no element ; so we use "text".
        		infonode = src;
            	infotype = src.getNodeName() ;
            	infovalue = s = src.getTextContent();
            	// Shall we try to convert the value ?
            	if (pty.equals("boolean"))
            	{
            		s = s.trim().toLowerCase();
            		infovalue = (s.equals("no") || s.equals("false") || s.equals("0")) ? false : true;
            		infotype = "boolean";
            	}
            	else
            	if (pty.equals("integer") || pty.equals("int"))
            	{
            		s = s.trim();
                    Integer ii;
                    try {
              	        ii = new Integer(s);
                    } catch (NumberFormatException e)
                    {
                    	  ii = new Integer(0);
                    }
                    infovalue = ii;
            		infotype = pty;
            	}
            	else
            	if (pty.equals("long"))
               	{
               		s = s.trim();
                    Long ii;
                    try {
                 	   ii = new Long(s);
                    } catch (NumberFormatException e)
                    {
                       ii = new Long(0);
                    }
                    infovalue = ii;
               		infotype = pty;
               	}
            	else
            	if (pty.equals("color") || pty.equals("bgcolor"))
            	{
                	s = s.trim();
                	infovalue = Ut.decodeColor(s);	
            		infotype = pty;
            	}
            	else
            	if (pty.equals("file"))
               	{
            		File f = new File(s);
                    infovalue = f;
               		infotype = pty;
               	}
            	else
            	if (pty.equals("time"))
               	{
            		long ll;
                    try {
                       Long ii;
                 	   ii = new Long(s.trim());
                 	   ll = ii.longValue();
                    } catch (NumberFormatException e)
                    {
                       ll = 0;
                    }
                    infovalue = new DisplayDate(ll);
               		infotype = pty;
               	}
            }
            else
            {
            	// We have an Element somewhere !
        		infonode = in;  // The actual content of the info element
            	infotype = in.getNodeName().toLowerCase() ;
            	if (infotype.equals("integer") || infotype.equals("int"))
            	{
            		s = Ut.getValue(in).trim();
                    Integer ii;
                    try {
              	        ii = new Integer(s);
                    } catch (NumberFormatException e)
                    {
                    	  ii = new Integer(0);
                    }
                    infovalue = ii;
                    infotype = "int";
            	}
            	else
            	if (infotype.equals("long"))
           	    {
           		   s = Ut.getValue(in).trim();
                   Long ii;
                   try {
             	        ii = new Long(s);
                   } catch (NumberFormatException e)
                   {
                   	  ii = new Long(0);
                   }
                   infovalue = ii;
           	    }
            	else
            	if (infotype.equals("boolean"))
            	{
            		s = in.getTextContent().trim().toLowerCase();
            		infovalue = (s.equals("no") || s.equals("false") || s.equals("0")) ? false : true;
            	}
            	else
                if (infotype.equals("anchor"))
                {
            		infovalue = in;
                }
                else
                if (infotype.equals("color") || infotype.equals("bgcolor"))
                {
                	s = in.getTextContent().trim();
                	infovalue = Ut.decodeColor(s);	
                }
                else
            	if (infotype.equals("time"))
            	{
            		DisplayDate dt = Ut.datize(in);
                    if (pty.equals("long"))
                    {
                    	infovalue = new Long(dt.getTime());
                    	infotype = "long";
                    }
                    else
                    {
                        infovalue = dt;
                    }
            	}
            	else
            	if (infotype.equals("img"))
            	{
            		s = ((Element)in).getAttribute("src").trim();
            		infovalue = new DisplayIcon(s);
            	}
            	else
                if (infotype.equals("icon"))
                {
                	s = in.getTextContent().trim();	
            		infovalue = new DisplayIcon(s);
                }
            	else
                if (infotype.equals("item") || infotype.equals("display"))
                {
            		infovalue = new DisplayItem((Element)in);
                }
            	else
            	if (infotype.equals("file"))
               	{
            		File f = new File(in.getTextContent().trim());
                    infovalue = f;
               	}
                else
            	{
                	// We end up giving the string content of the node
            	    infovalue = in.getTextContent();
            	}
            }
        }
		updated = true;
		return infotype;
	}
	
	/**
	 * This makes its best to decode the value inside the current info element ;
	 * it position the name, type, node and value.
	 * @return the type (a string) of the value
	 */
	public String updateInfoValue()
	{
		return updateInfoValue("");
	}

	/**
	 * Returns the number of "item" elements in a file
	 * @return an int, positive or null
	 */
	public int getItemCount()
	{
		return itemcnt;
	}
	
	/**
	 * This selects the first item in a display file
	 * An item node (or null) is available after this call
	 * @return a boolean
	 */
	public boolean selectFirstItem()
	{
		cinfo = null;
		citem = null;
		infoname = "";
		infotype = "";
		infovalue = null;
		infonode = null;
		updated = false;
		if (disp == null)
		{
			return false;
		}
		cnode = disp.getFirstChild();
        while (cnode != null
        && cnode.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE
        && ! cnode.getNodeName().equals(ITEM))
        {
        	cnode = cnode.getNextSibling();
        }
        if (cnode != null)
        {
     	   citem = (Element)cnode;
        }
	    return citem != null;
	}
	
	/**
	 * This selects the next item in a display file
	 * An item node (or null) is available after this call
	 * @return a boolean
	 */
	public boolean selectNextItem()
	{
		cinfo = null;
		infoname = "";
		infotype = "";
		infovalue = null;
		infonode = null;
		updated = false;
		if (citem == null)
		{
			return false;
		}
		cnode = citem.getNextSibling();
		citem = null;
        while (cnode != null
        && cnode.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE
        && ! cnode.getNodeName().equals(ITEM))
        {
        	cnode = cnode.getNextSibling();
        }
        if (cnode != null)
        {
     	   citem = (Element)cnode;
        }
	    return citem != null;
	}
	
	/**
	 * This indicates whether an item is currently available or not 
	 * @return a boolean
	 */
	public boolean itemAvailable()
	{
		return citem != null;
	}
	
	/**
	 * When a display file is open and an info item has been selected,
	 * this positions on the first info available for this item
	 * @return a boolean
	 */
	public boolean selectFirstInfo()
	{
		cinfo = null;
		infoname = "";
		infotype = "";
		infovalue = null;
		infonode = null;
		updated = false;
		if (citem == null)
		{
			System.out.println("*** selectFirstInfo(): no item selected");
			return false;
		}
		cnode = citem.getFirstChild();
        while (cnode != null
        && cnode.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE
        && ! cnode.getNodeName().equals(INFO))
        {
        	cnode = cnode.getNextSibling();
        }
        if (cnode != null)
        {
        	cinfo = (Element)cnode;
        	infoname = cinfo.getAttribute(NAME).trim().toLowerCase();
        }
	    return cinfo != null;
	}
	
	/**
	 * When a display file is open and an info element has been selected,
	 * this positions on the next info available for this item
	 * @return a boolean
	 */
	public boolean selectNextInfo()
	{
		infoname = "";
		infotype = "";
		infovalue = null;
		infonode = null;
		updated = false;
		if (cinfo == null)
		{
			// System.out.println("*** selectFirstInfo(): no item selected");
			return false;
		}
		cnode = cinfo.getNextSibling();
		cinfo = null;
        while (cnode != null
        && cnode.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE
        && ! cnode.getNodeName().equals(INFO))
        {
        	cnode = cnode.getNextSibling();
        }
        if (cnode != null)
        {
        	cinfo = (Element)cnode;
        	infoname = cinfo.getAttribute(NAME).trim().toLowerCase();
        }
	    return cinfo != null;
	}
	
	/**
	 * When a display file is open and an item has been selected,
	 * this positions on the first info available for this item
	 * whith the required name
	 * @return a boolean
	 */
	public boolean selectFirstInfo(String idt)
	{
		boolean r;
		String s = idt.trim().toLowerCase();
		// Special item case
		if (s.equals("#item#"))
		{
			return false;
		}
		r = selectFirstInfo();
		while (r && !s.equals(infoname))
			r = selectNextInfo();
		return r;
	}
	
	/**
	 * When a display file is open and an info element has been selected,
	 * this positions on the next info available for this item
	 * whith the required name
	 * @return a boolean
	 */
	public boolean selectNextInfo(String idt)
	{
		boolean r;
		String s = idt.trim().toLowerCase();
		// Special item case
		if (s.equals("#item#"))
		{
			return false;
		}
		do { r = selectNextInfo(); }
		while (r && !s.equals(infoname));
		return r;
	}
	
	/**
	 * This indicatew whether an "info" is currently available
	 * @return
	 */
	public boolean infoAvailable()
	{
		return cinfo != null;
	}
	
	/**
	 * This returns the name of the current information
	 * @return a string, or an empty string if no info is available
	 */
	public String infoName()
	{
		return infoname;
	}
	
	/**
	 * This returns the type (in the "display" concept) of
	 * the info currently available
	 * @return a string, or an empty string if no info is available
	 */
	public String infoType()
	{
		if (! updated)
		{
			updateInfoValue("");
		}
		return infotype;
	}
	
	/**
	 * This returns the value of the info currently available
	 * @return an object, possibly "null".
	 */
	public Object infoValue()
	{
		if (! updated)
		{
			updateInfoValue("");
		}
		return infovalue;
	}
	
	/**
	 * This returns the value of an attribute of
	 * the current value node, extracted from the current info node
	 * @param att : the name of the attribute
	 * @return a string or null;
	 */
	public String valueAttribute(String att)
	{
		String s = "";
		if (! updated)
		{
			updateInfoValue("");
		}
		if (infonode != null && infonode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE)
		{
			s = ((Element)infonode).getAttribute(att).trim();
		}
		return s;
	}
	
	public String getInfoAttribute(String att) {
		String s = "";
		if (! updated)
		{
			updateInfoValue("");
		}
		if (cnode!=null && cnode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE)
		{
			s = ((Element)cnode).getAttribute(att).trim();
		}
		return s;
	}
	
	public String getStringValueNoFail(String name) {
		String ret;
		if (!selectFirstInfo(name)) {
			return null;
		} 
		updateInfoValue("String");
		try {
			ret = (String) infoValue();
		} catch (ClassCastException cce) {
			return "";
		}
		
		return (ret==null)?"":ret;
	}
	
	public String getStringValue(String name, String msg) throws BadDisplayFile {
		String ret;
		if (!selectFirstInfo(name)) {
			throw new BadDisplayFile(msg);
		} 
		updateInfoValue("String");
		try {
			ret = (String) infoValue();
			if (ret==null) {
				ret = "";
			}
		} catch (ClassCastException cce) {
			throw new BadDisplayFile(msg);
		}
		
		return ret;
	}
	
	public int getIntValue(String name, String msg) throws BadDisplayFile {
		int ret;
		if (!selectFirstInfo(name)) {
			throw new BadDisplayFile(msg);
		} 
		if (!"int".equalsIgnoreCase(updateInfoValue("Integer"))) {
			throw new BadDisplayFile(msg);
		}
		try {
			ret = (Integer) infoValue();
		} catch (ClassCastException cce) {
			throw new BadDisplayFile(msg);
		}
		
		return ret;
	}
	
	public Integer getIntegerValue(String name, String msg) throws BadDisplayFile {
		Integer ret;
		if (!selectFirstInfo(name)) {
			throw new BadDisplayFile(msg);
		} 
		if (!"Integer".equalsIgnoreCase(updateInfoValue("Integer"))) {
			throw new BadDisplayFile(msg);
		}
		try {
			ret = (Integer) infoValue();
		} catch (ClassCastException cce) {
			throw new BadDisplayFile(msg);
		}
		
		return ret;
	}
	
	public Integer getIntegerValueNoFail(String name) {
		Integer ret;
		if (!selectFirstInfo(name)) {
			return null;
		} 
		if (!"int".equalsIgnoreCase(updateInfoValue("Integer"))) {
			return null;
		}
		try {
			ret = (Integer) infoValue();
		} catch (ClassCastException cce) {
			return null;
		}
		
		return ret;
	}
	
	public long getLongValue(String name, String msg) throws BadDisplayFile {
		long ret;
		if (!selectFirstInfo(name)) {
			throw new BadDisplayFile(msg);
		} 
		if (!"Long".equalsIgnoreCase(updateInfoValue("Long"))) {
			throw new BadDisplayFile(msg);
		}
		try {
			ret = (Long) infoValue();
		} catch (ClassCastException cce) {
			throw new BadDisplayFile(msg);
		}
		return ret;
	}
	
	public boolean getBooleanValue(String name, String msg) throws BadDisplayFile {
		boolean ret;
		if (!selectFirstInfo(name)) {
			throw new BadDisplayFile(msg);
		} 
		if (!"Boolean".equalsIgnoreCase(updateInfoValue("Boolean"))) {
			throw new BadDisplayFile(msg);
		}
		try {
			ret = (Boolean) infoValue();
		} catch (ClassCastException cce) {
			throw new BadDisplayFile(msg);
		}
		return ret;
	}
	
	public Anchor getAnchorValue(String name, String msg) throws BadDisplayFile {
		Anchor ret;
		if (!selectFirstInfo(name)) {
			throw new BadDisplayFile(msg);
		} 
		if (!"anchor".equalsIgnoreCase(updateInfoValue("anchor"))) {
			throw new BadDisplayFile(msg);
		}
		try {
			ret = new Anchor(infonode);
		} catch (ClassCastException cce) {
			throw new BadDisplayFile(msg);
		}
		return ret;
	}
	
	public String getType() {
		return disp.getAttribute(TYPE);
	}
	
}
